#include "cell.h"
#include "cell_func.h"
#include "defines.h"
#include "utils.h"
#include "net.h"
#include <string.h>
#include <stdlib.h>

struct Cell newNullCell() {
    struct Cell cell;
    cell = (const struct Cell){ 0 };
    return(cell);
}

void moveCell(struct World *w, int index, int x, int y) {
    memcpy(&w->cells[cordsToIndex(w, x, y)], &w->cells[index], sizeof(struct Cell));
    w->cells[index] = newNullCell();
    w->cells[index].type = T_NOTHING;
}

int cordsToIndex(struct World *w, int x, int y) {
    return(x+(w->width*y));
}

void indexToCords(struct World *w, int index, int *x, int *y) {
    *x = index % w->width;
    *y = index / w->width;
}

int eatFood(struct Cell *cell) {
    int energy = cell->energy;
    (*cell) = newNullCell();
    return(energy);
}

int newCell(struct World *w, int x, int y, struct Cell *cell, struct Cell *cellDest) {
    if(w != NULL) {
        int i = cordsToIndex(w, x, y);
        if(w->cells[i].type == T_NOTHING) {
            w->cells[i] = newNullCell();
            for(int j=0; j<GENOME_LENGTH; j++) {
                int g = cell->genome[j];
                w->cells[i].genome[j] = g;
            }
            w->cells[i].rotation = cell->rotation;
            w->cells[i].useMinerals = cell->useMinerals;
            w->cells[i].color = cell->color;
            w->cells[i].type = T_CELL;
            mutate(w, &w->cells[i]);
            return(i);
        }
    } else {
        memcpy(cellDest, cell, sizeof(struct Cell));
    }
    return(-1);
}

void teleportCell(struct Cell *cell, struct Net *n) {
    if(n == NULL) return;
    struct Package pkg;
    pkg.type = P_SEND;
    memcpy(&pkg.cell, cell, sizeof(struct Cell));
    N_send(n, pkg);
    killCell(cell, false);
}

void killCell(struct Cell *cell, bool willBeFood) {
    int energy = cell->energy;
    *cell = newNullCell();
    if(willBeFood) {
        cell->energy = energy;
        cell->energy *= 0.9;
        if(cell->energy < 1)
            cell->energy = 1;
        cell->type = T_FOOD;
        return;
    }
    cell->type = T_NOTHING;
}

void mutate(struct World *w, struct Cell* cell) {
    for(int i=0; i < 64; i++) {
        if((rand() % w->mutationChance) == 0) {
            int maxCode;
            if((rand() % 5) == 1) {
                maxCode = MAX_GENOME;
            } else {
                maxCode = G_END-1;
            }
            cell->genome[i] = rand() % maxCode;
            cell->color.r += 1-(rand() % 3);
            cell->color.g += 1-(rand() % 3);
            cell->color.b += 1-(rand() % 3);
        }
    }
}

void getRotation(int rotation, int* x, int* y){
    switch(rotation) {
    case(D_LUP):
        (*x)--;
        (*y)++;
        break;
    case(D_UP):
        (*y)++;
        break;
    case(D_RUP):
        (*x)++;
        (*y)++;
        break;
    case(D_LEFT):
        (*x)--;
        break;
    case(D_RIGHT):
        (*x)++;
        break;
    case(D_LDOWN):
        (*x)--;
        (*y)--;
        break;
    case(D_DOWN):
        (*y)--;
        break;
    case(D_RDOWN):
        (*x)++;
        (*y)--;
        break;
    }
}

void processGenome(struct World *w, struct Net *n, int index) {
    bool iLookAtWall = false;
    bool iLookAtCell = false;
    bool iLookAtFood = false;
    bool iLookAtMinwall = false;
    bool iLookAtAnything = false;
    bool iLookAtPortal = false;
    int x, y;
    indexToCords(w, index, &x, &y);
    int nx = x;
    int ny = y;

    getRotation(w->cells[index].rotation, &nx, &ny);

    if(ny < 0) {
        iLookAtWall = true;
    } else if (ny > w->height) {
        iLookAtWall = true;
    }

    if(nx < 0) nx = w->width-1;
    if(nx >= w->width) nx = 0;

    int iLookAtI;
    if(!iLookAtWall) {
        int c = cordsToIndex(w, nx, ny);
        switch(w->cells[c].type) {
        case(T_CELL):
            iLookAtCell = true;
            break;
        case(T_FOOD):
            iLookAtFood = true;
            break;
        case(T_WALL):
            iLookAtWall = true;
            break;
        case(T_MINWALL):
            iLookAtMinwall = true;
            break;
        case(T_PORTAL):
            iLookAtPortal = true;
            break;
        case(T_NOTHING):
            break;
        }
        iLookAtI = c;
    }
    iLookAtAnything = iLookAtCell || iLookAtWall || iLookAtFood || iLookAtMinwall || iLookAtPortal;

    int maxcolor = 0;
    int color;
    if(iLookAtCell) {
        if(w->cells[iLookAtI].color.r > maxcolor) {
            maxcolor = w->cells[iLookAtI].color.r;
            color = 0;
        }
        if(w->cells[iLookAtI].color.g > maxcolor) {
            maxcolor = w->cells[iLookAtI].color.g;
            color = 1;
        }
        if(w->cells[iLookAtI].color.b > maxcolor) {
            maxcolor = w->cells[iLookAtI].color.b;
            color = 2;
        }
    }

    struct Cell *cell = &w->cells[index];
    enum Genome code = cell->genome[cell->genomeIndex];
    // Genome
    int difference=0;
    switch(code) {
    case(G_SUN_ENERGY):
        if(y <= (w->height/2)+4) {
            float energy = 10;
            if(w->dayLength != -1) {
                energy *= ((float)w->time / (float)w->dayLength);
            }
            if(energy < 0) energy = 0.5;
            if(cell->useMinerals && cell->minerals > 0) {
                energy *= cell->minerals;
                cell->minerals--;
            }
            cell->energy += (int)energy;
            if(energy > 0) {
                cell->photosynthesisCount++;
            }
        }
        break;
    case(G_COLLECT_MINERALS):
        if(y >= (w->height/2)-4) {
            cell->minerals += 10;
        }
        break;
    case(G_MINERALS_ENERGY):
        if(cell->minerals > 0) {
            cell->minerals--;
            cell->energy += 15;
            cell->mineralsCount++;
        }
        break;
    case(G_MOVE):
        if(iLookAtPortal) {
            if(n != NULL)
                teleportCell(cell, n);
            break;
        }
        if(!iLookAtAnything) {
            moveCell(w, index, nx, ny);
        }
        cell->energy -= 10;
        break;
    case(G_ROTATE):
        cell->energy -= 5;
        cell->rotation++;
        if(cell->rotation > 7)
            cell->rotation = 0;
        break;
    case(G_LUP):
        cell->rotation = D_LUP;
        break;
    case(G_UP):
        cell->rotation = D_UP;
        break;
    case(G_RUP):
        cell->rotation = D_RUP;
        break;
    case(G_RIGHT):
        cell->rotation = D_RIGHT;
        break;
    case(G_RDOWN):
        cell->rotation = D_RDOWN;
        break;
    case(G_DOWN):
        cell->rotation = D_DOWN;
        break;
    case(G_LDOWN):
        cell->rotation = D_LDOWN;
        break;
    case(G_LEFT):
        cell->rotation = D_LEFT;
        break;
    case(G_CHECK_CELL):
        if(!iLookAtCell)
            cell->genomeIndex += 2;
        break;
    case(G_CHECK_WALL):
        if(!iLookAtWall)
            cell->genomeIndex += 2;
        break;
    case(G_CHECK_MINWALL):
        if(!iLookAtMinwall)
            cell->genomeIndex += 2;
        break;
    case(G_CHECK_FOOD):
        if(!iLookAtFood)
            cell->genomeIndex += 2;
        break;
    case(G_MAKE_CELL):
        if(!iLookAtAnything && cell->energy > w->maxEnergy/4) {
            int newIndex = newCell(w, nx, ny, &w->cells[index], NULL);
            if(newIndex == -1) cell->genomeIndex += 2;
            else {
                w->cells[newIndex].energy = cell->energy;
                w->cells[newIndex].energy /= 2;
            }
        } else if(iLookAtPortal) {
            struct Cell cellTmp;
            newCell(NULL, 0, 0, cell, &cellTmp);
            teleportCell(&cellTmp, n);
        }
        cell->energy--;
        break;
    case(G_KILL_CELL):
        if(iLookAtCell) {
            killCell(&w->cells[iLookAtI], true);
            w->cells[iLookAtI].energy /= 2;
        } else {
            cell->energy *= 0.5;
            cell->energy--;
        }
        break;
    case(G_EAT_FOOD):
        if(iLookAtFood) {
            cell->energy += eatFood(&w->cells[iLookAtI]);
            cell->attackCount++;
        } else {
            cell->energy *= PUNISHMENT;
            cell->energy--;
        }
        break;
    case(G_JUMP_GENOME):
        cell->genomeIndex += cell->genome[cell->genomeIndex+1];
        break;
    case(G_CHECK_GENOME):
        if(iLookAtCell) {
            for(int genomeIndex=0; genomeIndex < GENOME_LENGTH; genomeIndex++) {
                if(w->cells[iLookAtI].genome[genomeIndex] != cell->genome[genomeIndex]) difference++;
            }
            if(difference > 2) cell->genomeIndex += 2;
        }
        break;
    case(G_SHARE_ENERGY):
        if(iLookAtCell) {
            w->cells[iLookAtI].energy += (cell->energy /= 2);
        } else {
            cell->energy *= PUNISHMENT;
            cell->energy--;
        }
        break;
    case(G_BUILD_WALL):
        if(!iLookAtAnything && cell->minerals >= 100) {
            cell->minerals -= 100;
            w->cells[cordsToIndex(w, nx, ny)] = newNullCell();
            w->cells[cordsToIndex(w, nx, ny)].type = T_MINWALL;
            w->cells[cordsToIndex(w, nx, ny)].energy = 1;
        }
        break;
    case(G_BREAK_WALL):
        if(iLookAtMinwall)
            w->cells[cordsToIndex(w, nx, ny)].energy--;
        else {
            cell->energy *= PUNISHMENT;
            cell->energy--;
        }
        break;
    case(G_INFECT):
        if(iLookAtCell) {
            for(int g=0; g<10; g++) {
                w->cells[iLookAtI].genome[(w->cells[iLookAtI].genomeIndex+g) % GENOME_LENGTH] = cell->genome[(cell->genomeIndex+g) % GENOME_LENGTH];
                if(!((rand() % 10)-1))
                    w->cells[iLookAtI].genome[(w->cells[iLookAtI].genomeIndex+g) % GENOME_LENGTH] = rand() % MAX_GENOME;
            }
        } else {
            cell->energy *= PUNISHMENT;
            cell->energy--;
        }
        break;
    case(G_CHECK_RED):
        if(iLookAtCell) {
            if(color != 0)
                cell->genomeIndex += 2;
        } else {
            cell->energy *= PUNISHMENT;
            cell->energy--;
        }
        break;
    case(G_CHECK_GREEN):
        if(iLookAtCell) {
            if(color != 1)
                cell->genomeIndex += 2;
        } else {
            cell->energy *= PUNISHMENT;
            cell->energy--;
        }
        break;
    case(G_CHECK_BLUE):
        if(iLookAtCell) {
            if(color != 2)
                cell->genomeIndex += 2;
        } else {
            cell->energy *= PUNISHMENT;
            cell->energy--;
        }
        break;
    case(G_USE_MINERALS):
        cell->useMinerals = true;
        break;
    case(G_DONT_USE_MINERALS):
        cell->useMinerals = false;
        break;
    case(G_END):
        break;
    }
    cell->genomeIndex++;
    cell->genomeIndex %= GENOME_LENGTH;
}
